<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>补充-手写apply-call函数实现</title>
  <!-- 
  在JavaScript中， `call` 和 `apply` 是函数对象的方法，用于改变函数的上下文（即 `this` 的指向）。这两个方法的主要区别在于传递参数的方式： `call` 接收参数列表，而 `apply` 接收一个数组。
  1.  `call`  方法的实现
  `call`  方法可以通过将函数的上下文设置为指定的对象并立即调用该函数来实现。我们可以使用  `Function.prototype`  来扩展  `call`  方法。
  Function.prototype.myCall = function(context, ...args) {
      // 如果 context 为 null 或 undefined，默认指向 globalThis
      context = context || globalThis;

      // 将函数绑定到 context 上
      context.fn = this;

      // 调用函数并获取返回值
      const result = context.fn(...args);

      // 删除临时属性
      delete context.fn;

      return result;
  };
   使用示例：
  function greet(greeting) {
      console.log(`${greeting}, my name is ${this.name}`);
  }

  const person = { name: 'Alice' };
  greet.myCall(person, 'Hello'); // 输出: Hello, my name is Alice
  2.  `apply`  方法的实现

  `apply`  方法与  `call`  方法类似，但它接受一个数组作为参数，而不是参数列表。实现逻辑与  `call`  方法相似。
  Function.prototype.myApply = function(context, args) {
      // 如果 context 为 null 或 undefined，默认指向 globalThis
      context = context || globalThis;

      // 将函数绑定到 context 上
      context.fn = this;

      // 调用函数并获取返回值
      const result = context.fn(...(args || [])); // 如果 args 为 null，使用空数组

      // 删除临时属性
      delete context.fn;

      return result;
  };
   使用示例：
  function greet(greeting) {
      console.log(`${greeting}, my name is ${this.name}`);
  }

  const person = { name: 'Bob' };
  greet.myApply(person, ['Hi']); // 输出: Hi, my name is Bob
  3. 总结
  -  `myCall`  和  `myApply`  方法的实现：这两个方法通过将函数的上下文设置为指定的对象并调用该函数来实现。 `myCall`  接受参数列表，而  `myApply`  接受一个数组。
  - 上下文的处理：在实现中，我们确保在  `context`  为  `null`  或  `undefined`  时，默认将其设置为  `globalThis` ，以确保函数能够正常执行。
  - 临时属性的清理：在调用函数后，我们删除了临时添加的属性，以避免污染对象。
  - 灵活性：通过手动实现这两个方法，我们更深入地理解了 JavaScript 中的函数上下文和作用域链。
   -->
</head>
<body>

  <script>

    function foo(name, age, ...otherArgs) {
      console.log(this, name, age, ...otherArgs)
      return 111
    }

    // foo函数可以通过apply/call
    // foo.apply("aaa", ["why", 18])
    // foo.call("bbb", "kobe", 30)

    // 1. createApply 函数的实现
    Function.prototype.createApply = function(context, args) {
      // 如果 context 为 null 或 undefined，默认指向 globalThis
      context = context || globalThis;

      console.log(this) // foo;
      
      // 将函数绑定到 context 上
      context.fn = this;

      // 1. 获取context, 并且确保是一个对象类型
      context = (context === null || context === undefined) ? window : Object(context);
      // const result = context.fn(...(args || [])); // 如果 args 为 null，使用空数组

      // content.fn = this
      Object.defineProperty(context, 'fn', {
        value: this,
        writable: true, // 可写(可修改)
        configurable: true, // 可配置(可删除)
        enumerable: false // 不可枚举(for...in遍历不到)
      })
      const res = context.fn(...args);

      delete context.fn;

      return res;
    };

    const res = foo.createApply({name: "createApply"}, ["apply", 18])
    console.log(res);
    

    // 2. createCall 函数的实现
    Function.prototype.createCall = function(context, ...args) {
      // 如果 context 为 null 或 undefined，默认指向 globalThis
      context = context || globalThis;

      // 将函数绑定到 context 上
      context.fn = this;

      // 1. 获取context, 并且确保是一个对象类型
      context = (context === null || context === undefined) ? window : Object(context);
      // const result = context.fn(...(args || [])); // 如果 args 为 null，使用空数组

      // content.fn = this
      Object.defineProperty(context, 'fn', {
        value: this,
        writable: true, // 可写(可修改)
        configurable: true, // 可配置(可删除)
        enumerable: false // 不可枚举(for...in遍历不到)
      })
      const res = context.fn(...args);

      delete context.fn;

      return res;
    }

    const result = foo.createCall({name: "createCall"}, "call", 18)
    console.log(result);
    
    // 3. createBind 函数的实现
    Function.prototype.createBind = function(context, ...otherArgs) {
      // 如果 context 为 null 或 undefined，默认指向 globalThis
      context = context || globalThis;

      // 将函数绑定到 context 上
      context.fn = this;

      // 1. 获取context, 并且确保是一个对象类型
      context = (context === null || context === undefined) ? window : Object(context);
      // const result = context.fn(...(args || [])); // 如果 args 为 null，使用空数组

      // content.fn = this
      Object.defineProperty(context, 'fn', {
        value: this,
        writable: true, // 可写(可修改)
        configurable: true, // 可配置(可删除)
        enumerable: false // 不可枚举(for...in遍历不到)
      })

      return (...args) => {
        const allArgs = [...otherArgs, ...args];
        const res = context.fn(...allArgs);

        return res;
      }
    }

    const resBind = foo.createBind({name: "createBind"}, "bind", 18)
    console.log(resBind('bind1', 19));
    console.log(resBind('bind2', 29));

    

    
  </script>
  
</body>
</html>